//#include "stdafx.h"

#include "SceneIlluminationMap.h"
#include "CAdvancedMeshCollision.h"
#include "IWriteFile.h"
#include "IReadFile.h"

#include "IlumpUtils.h"
#include "Core/Commons.h"

#ifndef PFEXPORT
#include "Application.h"
#else
#include "PFExporter.h"
#endif

#define PFEXPORTER_VERSION (20100608)

/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////


SceneIlluminationMap::SceneIlluminationMap() :
	m_pos(0,0,0),
	m_tileSize(0),
	m_tileSizeZ(0),
	m_sizeX(0),
	m_sizeY(0),
	m_sizeZ(0),
	m_hgrid(0),
	m_minValue(0),
	m_maxValue(255),
	m_rgbScale(1.0f,1.0f,1.0f)
{
	
}


SceneIlluminationMap::~SceneIlluminationMap()
{	
	SAFE_DEL(m_hgrid);
}


#ifdef PFEXPORT

// --------------------------------------------------------------------------
/// @brief Performs the lightmap computation according to the mesh
// --------------------------------------------------------------------------
void SceneIlluminationMap::Compute(RoomMapInfo& info, s32 tileSize, s32 tileSizeZ)
{
	// set map position
	const irr::core::aabbox3df& box = info.AABBox;
	m_pos = box.MinEdge;
	
	//calculate map dimensions
	irr::core::vector3df extent = box.getExtent();
	m_sizeX = static_cast<s32>(extent.X) / tileSize;
	m_sizeY = static_cast<s32>(extent.Y) / tileSize;
	m_sizeZ = static_cast<s32>(extent.Z) / tileSize;
	
	m_tileSize = tileSize;
	m_tileSizeZ = tileSizeZ;
	
	u8* map = NEW u8[m_sizeX * m_sizeY * m_sizeZ];
	u8* occupation = NEW u8[m_sizeX * m_sizeY * m_sizeZ];
	
	// create mesh collisions from geometry
	CAdvancedMeshCollision** collision_array = info.collision_array;
	s32 collision_num = info.collision_num;	
	
	// mark only the potential occupated tiles (to speed up colliison checks)
	irr::core::array<irr::core::triangle3df> triangles;
	Utils::GetMeshCollisionTriangles(triangles, collision_array, collision_num);
	for(s32 i = 0; i < m_sizeX; i++)
	{
		for(s32 j = 0; j < m_sizeY; j++)
		{
			for(s32 k = 0; k < m_sizeZ; k++)
			{
				occupation[GetTileIndex(i, j, k)] = 0;
			}
		}
	}
	
	for(s32 i = 0; i < triangles.size(); i++)
	{
		s32 minx, miny, minz, maxx, maxy, maxz;
		s32 x1, y1, z1;
		WorldToTilePosition(triangles[i].pointA, x1, y1, z1);
		minx = x1;
		miny = y1;
		minz = z1;
		maxx = x1;
		maxy = y1;
		maxz = z1;
		WorldToTilePosition(triangles[i].pointB, x1, y1, z1);
		if(x1 < minx) minx = x1;
		if(x1 > maxx) maxx = x1;
		if(y1 < miny) miny = y1;
		if(y1 > maxy) maxy = y1;
		if(z1 < minz) minz = z1;
		if(z1 > maxz) maxz = z1;
		WorldToTilePosition(triangles[i].pointC, x1, y1, z1);
		if(x1 < minx) minx = x1;
		if(x1 > maxx) maxx = x1;
		if(y1 < miny) miny = y1;
		if(y1 > maxy) maxy = y1;
		if(z1 < minz) minz = z1;
		if(z1 > maxz) maxz = z1;
		
		for(s32 t = minx - 1; t <= maxx + 1; t++)
		{
			for(s32 j = miny - 1; j <= maxy + 1; j++)
			{
				for(s32 k = minz - 1; k <= maxz + 1; k++)
				{
					if(t >= 0 && j >= 0 && k >= 0 && t < m_sizeX && j < m_sizeY && k < m_sizeZ)
					{
						occupation[GetTileIndex(t, j, k)] = 1;
					}
				}
			}
		}
	}
	
	
	
	// for each tile - get the closest collision with the geometry meshes
	for(s32 i = 0; i < m_sizeX; i++)
	{
		for(s32 j = 0; j < m_sizeY; j++)
		{
			for(s32 k = 0; k < m_sizeZ; k++)
			{
				// initialize value
				map[GetTileIndex(i, j, k)] = 255;
				
				if(occupation[GetTileIndex(i, j, k)] == 1)
				{
					// construct ray in z-down direction from the tile
					irr::core::vector3df rayOrigin = TileToWorldPosition(i, j, k);
					rayOrigin.Z += m_tileSizeZ;
					irr::core::vector3df rayDir = irr::core::vector3df(0, 0, -1);
					rayDir.Z = -m_tileSizeZ * 2;
					
					irr::core::line3d<irr::f32> ray;
					ray.start = rayOrigin;
					ray.end = rayOrigin + rayDir;
				
					// get the closest collision to the tile
					irr::core::vector3df collisionPoint;
					irr::core::triangle3df collisionTriangle;
					u32 color;
					bool res = Utils::GetMeshCollisionIntersectWithColor(ray, collision_array, collision_num, 
						collisionPoint, collisionTriangle, color);
				
					//tile has illumination value
					if(res)
					{
						//u32 color = GetInterpolatedColor(collisionPoint, collisionTriangle, color1, color2, color3);
						map[GetTileIndex(i, j, k)] = GetGrayscale(color);
						//DBG("tile %d %d %d, colors = %x, %x, %x\n", i, j, k, color1, color2, color3);
						//DBG("tile %d %d %d, colors = %d %x\n", i, j, k, map[GetTileIndex(i, j, k)], color);
					}
				}
			}
		}
	}
		
	
	
	
	// fix jump issue comming from the absense of z color interpolation
	for(s32 i = 0; i < m_sizeX; i++)
	{
		for(s32 j = 0; j < m_sizeY; j++)
		{
			for(s32 k = 1; k < m_sizeZ; k++)
			{
				s32 value = map[GetTileIndex(i, j, k)];
				
				if(value == 0)
				{
					continue;
				}

				if(map[GetTileIndex(i, j, k - 1)] == 0)
				{
					if(HasNeighbours(map, i, j, k - 1))
					{
						map[GetTileIndex(i, j, k - 1)] = value;
					}
				}
			}
			
			for(s32 k = m_sizeZ - 2; k >= 0; k--)
			{
				s32 value = map[GetTileIndex(i, j, k)];
				
				if(value == 0)
				{
					continue;
				}
			
				if(map[GetTileIndex(i, j, k + 1)] == 0)
				{
					if(HasNeighbours(map, i, j, k + 1))
					{
						map[GetTileIndex(i, j, k + 1)] = value;
					}
				}
			}
		}
	}
	

	//SaveAscii("LightMap1.txt", map);
	
	m_hgrid = NEW LightHGrid(map, m_sizeX, m_sizeY, m_sizeX, m_sizeY, m_sizeZ);
	
	SAFE_DEL_ARRAY(map);
	SAFE_DEL_ARRAY(occupation);
	//DBG("finished\n");
}

#endif //#ifdef PFEXPORT

// --------------------------------------------------------------------------
/// @brief Checks if a specified tile has valuable neighbours on this z axis
// --------------------------------------------------------------------------
bool SceneIlluminationMap::HasNeighbours(u8* map, s32 i, s32 j, s32 k)
{
	for(s32 m = -1; m <= 1; m++)
	{
		for(s32 n = -1; n <= 1; n++)
		{
			if(i + m >= 0 && i + m < m_sizeX && j + n >= 0 && j + n < m_sizeY)
			{
				if(map[GetTileIndex(i + m, j + n, k)] != 0)
				{
					return true;
				}
			}
		}
	}
	return false;
}

// --------------------------------------------------------------------------
/// @brief Returns the real world position of the given tile
// --------------------------------------------------------------------------
irr::core::vector3df SceneIlluminationMap::TileToWorldPosition(s32 i, s32 j, s32 k)
{
	return irr::core::vector3df(m_pos.X + i * m_tileSize + m_tileSize/2,
								m_pos.Y + j * m_tileSize + m_tileSize/2,
								m_pos.Z + k * m_tileSizeZ + m_tileSizeZ/2);
}

// --------------------------------------------------------------------------
/// @brief Returns the tile index on the real world position
// --------------------------------------------------------------------------
void SceneIlluminationMap::WorldToTilePosition(const irr::core::vector3df& pos, s32& i, s32& j, s32& k)
{
	i = (pos.X - m_pos.X);
	j = (pos.Y - m_pos.Y);
	k = (pos.Z - m_pos.Z);
	i /=  m_tileSize;
	j /=  m_tileSize;
	k /=  m_tileSizeZ;
	
	// ensure that we're in the map boundaries 
	if(i < 0) i = 0;
	if(j < 0) j = 0;
	if(k < 0) k = 0;
	if(i >= m_sizeX) i = m_sizeX - 1;
	if(j >= m_sizeY) j = m_sizeY - 1;
	if(k >= m_sizeZ) k = m_sizeZ - 1;
}


// --------------------------------------------------------------------------
/// @brief Returns the tile index in the one dimensional array
// --------------------------------------------------------------------------
s32 SceneIlluminationMap::GetTileIndex(s32 i, s32 j, s32 k)
{
	return (k * m_sizeY + j) * m_sizeX + i;
}

// --------------------------------------------------------------------------
/// @brief Convert an rgb color to grayscale
// --------------------------------------------------------------------------
u8 SceneIlluminationMap::GetGrayscale(u32 color)
{
	irr::video::SColor c(color);
	f32 lum = c.getLuminance();
	u8 value = static_cast<s32>(lum);
	
	// we need an empty value for the hierarchical grid (0)
	// TODO: check if this is really needed
	//if(value == 0)
	//{
	//	value = 1;
	//}
	return value;
}


//-------------------------------------------------------------------------------------------------------------------------------------------------------------
/// @brief Returns the illumination value on the specified position
/// Using bilinear interpolation
//-------------------------------------------------------------------------------------------------------------------------------------------------------------
s32 SceneIlluminationMap::GetIllumination(const irr::core::vector3df& pos)
{
	s32 i, j, k;
	WorldToTilePosition(pos, i, j, k);
	irr::core::vector3df pos2 = TileToWorldPosition(i, j, k);
	irr::core::vector3df diff = pos - pos2;
	
	s32 i1 = (diff.X > 0) ? (i + 1) : (i - 1);
	s32 j1 = (diff.Y > 0) ? (j + 1) : (j - 1);
	if(i1 < 0) i1 = 0;
	if(j1 < 0) j1 = 0;
	if(i1 >= m_sizeX) i1 = m_sizeX - 1;
	if(j1 >= m_sizeY) j1 = m_sizeY - 1;

	f32 dx = fabs(diff.X) / m_tileSize;
	f32 dy = fabs(diff.Y) / m_tileSize;
	f32 invdx = (1.0f - dx);
	f32 invdy = (1.0f - dy);
	
	// bilinear interpolation
	f32 fvalue = invdx * invdy * m_hgrid->Get(i, j, k) + 
				invdx * dy * m_hgrid->Get(i, j1, k) +
				dx * invdy * m_hgrid->Get(i1, j, k) +
				dx * dy * m_hgrid->Get(i1, j1, k);

	fvalue +=m_valueOffset;
	if(fvalue<0)
		fvalue=0;
	else if(fvalue>255)
		fvalue=255;
	
	f32 p = fvalue / 255.0f;
	s32 v = m_minValue*(1-p) + m_maxValue*p;
	return v;

	//irr::video::SColor c = m_maxValue.getInterpolated(m_minValue, fvalue / 255.0f);
	//return c.toARGB8();
}


#ifndef PFEXPORT
//-------------------------------------------------------------------------------------------------------------------------------------------------------------
/// @brief Loads the map from a binary file
//-------------------------------------------------------------------------------------------------------------------------------------------------------------
void SceneIlluminationMap::LoadFromFile(const char* filename)
{		
	IrrlichtDevice* pDevice = Application::instance()->GetDevice();	

	irr::io::IReadFile *stream = pDevice->getFileSystem()->createAndOpenFile(filename);
		

	s32 PFExporterVersion;
	stream->read(&PFExporterVersion, sizeof(PFExporterVersion));	

	u8 min,max;
	stream->read(&min, sizeof(u8));
	stream->read(&max, sizeof(u8));
	m_minValue = min;
	m_maxValue = max;	

	stream->read(&m_valueOffset, sizeof(s16));		
	
	stream->read(&m_sizeX, sizeof(s32));
	stream->read(&m_sizeY, sizeof(s32));
	stream->read(&m_sizeZ, sizeof(s32));
	stream->read(&m_tileSize, sizeof(s32));

	if(PFExporterVersion>=20100608)
		stream->read(&m_tileSizeZ, sizeof(s32));
	else
		m_tileSizeZ = m_tileSize;

	
	f32 tmp;
	stream->read(&tmp, sizeof(f32));
	m_pos.X = tmp;
	stream->read(&tmp, sizeof(f32));
	m_pos.Y = tmp;
	stream->read(&tmp, sizeof(f32));	
	m_pos.Z = tmp;

	float r,g,b;	
	stream->read(&r,sizeof(f32));	
	stream->read(&g,sizeof(f32));	
	stream->read(&b,sizeof(f32));
	m_rgbScale.set(r,g,b);

	DEBUG_OUT("load %s, min=%d,max=%d,offset=%d,tileSize=%d,tileSizeZ=%d,r=%f,g=%f,b=%f",filename,m_minValue,m_maxValue,m_valueOffset,m_tileSize,m_tileSizeZ,r,g,b);

	
	m_hgrid = NEW LightHGrid(*stream);

	stream->drop();
}
#endif //#ifndef PFEXPORT


#ifdef PFEXPORT
//-------------------------------------------------------------------------------------------------------------------------------------------------------------
/// @brief Saves a debug view of the map
//-------------------------------------------------------------------------------------------------------------------------------------------------------------
void SceneIlluminationMap::SaveAscii(const char* fileName, u8* arr)
{
	FILE * file = fopen(fileName, "w");
	fprintf(file, "light map size = %d %d %d\n", m_sizeX, m_sizeY, m_sizeZ);
	for(s32 k = 0; k < m_sizeZ; ++k)
	{
		fprintf(file, "\n");
		for(s32 j = 0; j < m_sizeY; ++j)
		{
			for(s32 i = 0; i < m_sizeX; ++i)
			{
				u8 value = arr ? arr[GetTileIndex(i, j, k)] : m_hgrid->Get(i, j, k);
				if(value == 0)
				{
					fprintf(file, "     ");
				}
				else
				{
					fprintf(file, "[%03d]", value);
				}
			}
			fprintf(file, "\n");
		}
	}
	fclose(file);
}

//-------------------------------------------------------------------------------------------------------------------------------------------------------------
/// @brief Saves the binary map
//-------------------------------------------------------------------------------------------------------------------------------------------------------------
void SceneIlluminationMap::SaveBinary(irr::io::IWriteFile& stream)
{	
	int ver = PFEXPORTER_VERSION;
	stream.write(&ver,sizeof(s32));

	int value = PFExporter::GetMinValue();
	stream.write(&value, sizeof(u8));
	value = PFExporter::GetMaxValue();
	stream.write(&value, sizeof(u8));
	value = PFExporter::GetOffset();
	stream.write(&value, sizeof(s16));


	stream.write(&m_sizeX,sizeof(s32));
	stream.write(&m_sizeY,sizeof(s32));
	stream.write(&m_sizeZ,sizeof(s32));
	stream.write(&m_tileSize,sizeof(s32));
	stream.write(&m_tileSizeZ,sizeof(s32));
	f32 tmp;
	tmp = m_pos.X;
	stream.write(&tmp,sizeof(f32));
	tmp = m_pos.Y;
	stream.write(&tmp,sizeof(f32));
	tmp = m_pos.Z;
	stream.write(&tmp,sizeof(f32));

	float r,g,b;
	PFExporter::GetRGBScale(r,g,b);	
	stream.write(&r,sizeof(f32));	
	stream.write(&g,sizeof(f32));	
	stream.write(&b,sizeof(f32));

	
	m_hgrid->Export(stream);
}
#endif //PFEXPORT